/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import { toNumber } from '@baize/wdk';
import fs, { Stats } from 'fs';
import { AddressInfo } from 'net';
import * as path from 'path';
import process from 'process';

import {
  DAEMON_LOG_FILE_NAME_PATTEN,
  DAEMON_LOG_FILE_RETENTION_TIME,
  DAEMON_LOG_PATH,
  DAEMON_PROCESS_EXIT_TIME_OUT,
  DAEMON_PROCESS_REMOVED_FROM_REGISTRY_CHECK_TIME,
} from '../const/daemon-const.js';
import { HvigorDaemonLogger } from '../log/hvigor-daemon-log.js';
import { DaemonInfo, DaemonState } from '../registry/daemon-info.js';
import { defaultDaemonInfoService } from '../registry/daemon-info-service.js';
import { defaultDaemonServerRegistry } from '../registry/daemon-server-registry.js';
import { DaemonServerEventName, HvigorDaemonServer } from '../server/hvigor-daemon-server.js';

let isExiting = false;
let timeout: NodeJS.Timeout;
const _log = HvigorDaemonLogger.getLogger('daemon');

// process 启动后设置当前process状态为IDLE
let currentProcessState: DaemonState = DaemonState.IDLE;

/**
 * 获取当前daemon状态
 *
 * @returns {DaemonState}
 */
export function getCurrentProcessState() {
  return currentProcessState;
}

/**
 * 更新当前daemon状态
 *
 * @param {DaemonState} daemonState
 * @param stateChangeInfo
 */
export function setCurrentProcessState(daemonState: DaemonState, stateChangeInfo?: string) {
  currentProcessState = daemonState;
  defaultDaemonServerRegistry.updateDaemonProcessState(currentProcessState, stateChangeInfo);
}

/**
 * 初始化设置定时器
 */
export function setProcessTimeOutTimer(hvigorDaemonServer: HvigorDaemonServer) {
  timeout = setInterval(() => {
    if (getCurrentProcessState() === DaemonState.IDLE) {
      _log.debug('The process will exit automatically because it has been idle for more than 3 hours.');

      // 设置process的状态信息为stopped,防止直接从注册表中直接删除信息,避免进程没有退出,信息丢失
      setCurrentProcessState(DaemonState.STOPPED, 'process idle timeout');
      handleExit(-1, hvigorDaemonServer);
    } else {
      _log.debug(`The process has not been used for more than 3 hours,' +
        ' but will not exit because the current status is ${getCurrentProcessState()}`);
    }
  }, DAEMON_PROCESS_EXIT_TIME_OUT);
}

/**
 * 刷新计时器
 */
export function resetProcessTimeOutTimer(hvigorDaemonServer: HvigorDaemonServer) {
  if (timeout !== undefined) {
    clearInterval(timeout);
  }
  _log.debug('The process timeout timer will be reset.');
  setProcessTimeOutTimer(hvigorDaemonServer);
}

/**
 * 监控daemon cache中自身信息的正确性
 */
export function setProcessRemovedFromRegistryTimer(hvigorDaemonServer: HvigorDaemonServer) {
  setInterval(() => {
    const daemonInfos = defaultDaemonServerRegistry.getInfos();

    // 注册表中不存在该进程的信息
    const curProcessIsInValidInRegistry = !daemonInfos.some((daemonInfo) => {
      return daemonInfo.pid === process.pid;
    });

    if (curProcessIsInValidInRegistry) {
      _log.debug('hvigor daemon: Current process is not found in the registry, the process will automatically exit.');
      const daemonProcessStateChangeInfo = 'process is broken by user or operating system.';

      // 最后更新注册表信息，设置该daemon为broken状态
      const brokenDaemonInfo: DaemonInfo = {
        ...defaultDaemonInfoService.createDefaultDaemonInfo(hvigorDaemonServer.getHttpServer().address() as AddressInfo, hvigorDaemonServer.getSessionId()),
        state: DaemonState.BROKEN,
        info: daemonProcessStateChangeInfo,
      };

      defaultDaemonServerRegistry.setInfo(brokenDaemonInfo.keyId, brokenDaemonInfo);
      handleExit(-1, hvigorDaemonServer);
    } else {
      const curDaemonInfoState = defaultDaemonServerRegistry.getDaemonInfoByPid(process.pid)!.state;
      if (curDaemonInfoState === DaemonState.STOPPED || curDaemonInfoState === DaemonState.BROKEN) {
        _log.debug('hvigor daemon: Current process is stopped or broken. The process will exit.');

        // process状态在注册表中已标记为stopped或broken,不需要更新状态,直接退出
        handleExit(-1, hvigorDaemonServer);
      } else {
        _log.debug(`hvigor daemon: Check daemon process existed in registry, daemonStatus=${curDaemonInfoState}.`);
      }
    }
  }, DAEMON_PROCESS_REMOVED_FROM_REGISTRY_CHECK_TIME);
}

function handleDaemonLog() {
  function isDaemonLogFile(logFileStats: Stats, name: string) {
    return logFileStats.isFile() && DAEMON_LOG_FILE_NAME_PATTEN.test(name);
  }

  function getDaemonPidByLogFileName(logFileName: string): number {
    const indexOfStartPid = logFileName.indexOf('-');
    const indexOfEndPid = logFileName.indexOf('.');
    if (indexOfEndPid <= indexOfStartPid) {
      return -1;
    }
    return toNumber(logFileName.substring(indexOfStartPid + 1, indexOfEndPid));
  }

  function isNotActiveDaemonProcessAndOverTheLimitTime(daemonPid: number, logFileLastModifiedTime: Date) {
    const daemonInfoByPid = defaultDaemonServerRegistry.getDaemonInfoByPid(daemonPid);
    const curTime = Date.now();
    const logFileLastModifiedMillTime = logFileLastModifiedTime.getTime();

    // 当且仅当日志文件对应的pid的进程不在注册表中,并且距离上次修改时间已经超过时间上限,则为历史无用日志文件
    return daemonInfoByPid === undefined && curTime - logFileLastModifiedMillTime > DAEMON_LOG_FILE_RETENTION_TIME;
  }

  fs.readdirSync(DAEMON_LOG_PATH).forEach((name) => {
    const logFilePath = path.resolve(DAEMON_LOG_PATH, name);
    const logFileStats = fs.statSync(logFilePath);

    if (isDaemonLogFile(logFileStats, name)) {
      const daemonPid = getDaemonPidByLogFileName(name);
      const logFileLastModifiedTime = logFileStats.mtime;
      if (isNotActiveDaemonProcessAndOverTheLimitTime(daemonPid, logFileLastModifiedTime)) {
        try {
          fs.unlinkSync(logFilePath);
          _log.debug(`Delete log file, logPath=${logFilePath}.`);
        } catch (e: any) {
          _log.debug('Cannot delete log file. The file may be opened by another program. ' + `logPath=${logFilePath}, errorMessage=${e.message}`);
        }
      }
    }
  });
}

export function registryProcessListener(hvigorDaemonServer: HvigorDaemonServer) {
  process.on('exit', (code) => {
    _log.debug('hvigor daemon: Daemon process exit.');

    if (getCurrentProcessState() !== DaemonState.STOPPED || getCurrentProcessState() !== DaemonState.BROKEN) {
      // 设置process的状态信息为stopped,防止直接从注册表中直接删除信息,避免进程没有退出,信息丢失
      setCurrentProcessState(DaemonState.STOPPED, `stopped by exit event with code: ${code}`);
    }

    handleExit(code, hvigorDaemonServer);
  });

  const signalHandleListener = (exitCode: number, error?: Error | unknown) => {
    // 设置process的状态信息为stopped,防止直接从注册表中直接删除信息,避免进程没有退出,信息丢失
    setCurrentProcessState(DaemonState.STOPPED, `stopped by signal: ${exitCode}`);
    handleExit(exitCode, hvigorDaemonServer, error);
  };

  // 用 128 + 信号编号 得到最终的退出码
  process.on('SIGHUP', () => signalHandleListener(128 + 1));
  process.on('SIGINT', () => signalHandleListener(128 + 2));
  process.on('SIGTERM', () => signalHandleListener(128 + 15));

  // 在 Windows 中按下 <Ctrl>+<Break> 会被触发
  process.on('SIGBREAK', () => signalHandleListener(128 + 21));

  // 退出码 1 代表未捕获的错误导致进程退出
  process.on('uncaughtException', (error) => signalHandleListener(1, error));
  process.on('unhandledRejection', (error) => signalHandleListener(1, error));
}

/**
 * 在daemon进程退出前完成相应处理动作，如维护注册表信息
 * @param {number} exitCode
 * @param {HvigorDaemonServer} hvigorDaemonServer
 * @param error
 */
function handleExit(exitCode: number, hvigorDaemonServer: HvigorDaemonServer, error?: Error | unknown) {
  if (isExiting) return;
  isExiting = true;

  let hasDoExit = false;
  const doExit = () => {
    _log.debug(`hvigor daemon: Daemon process exit. exitCode=${exitCode}`);
    if (hasDoExit) return;
    hasDoExit = true;
    setImmediate(() => process.exit(exitCode));
  };
  if (error) {
    _log.error(`hvigor daemon: Exit with error: ${error}`);
    if (error instanceof Error && error.stack) {
      _log.error(`hvigor daemon: Stack: ${error.stack}`);
    }
  }

  // 关闭daemon server并通知对应的client socket
  hvigorDaemonServer.emit(DaemonServerEventName.CLOSE_SERVER, error);

  // 每个daemon退出之前检测一下daemon的历史日志
  handleDaemonLog();

  setTimeout(() => {
    _log.debug('hvigor daemon: Set process exit timeout in 1000 ms.');
    doExit();
  }, 1000);
}
